package server;

import java.net.Socket;
import java.util.ArrayList;

import common.ControlMessage;
import common.Logger;
import common.Message;
import common.ConnectionManager;
import common.MessageType;

public class ClientService extends Thread {
	
	private Logger log = null;
	
	private ConnectionManager connManager = null;
	
	private boolean running = false;
	private boolean backup = false;
	private boolean disconnectMsg = false;
	
	private boolean logged;
	
	private String clientName;
	
	private Server server;
	
	private RoomManager roomManager = null;
	
	public ClientService(Server server, Socket socket, Logger log) {
		this.server = server;
		this.connManager = new ConnectionManager(socket);
		this.log = log;
		this.roomManager = RoomManager.getInstance(log);
		
		this.start();
	}
	
	public void close() {
		unregisterClient();
	}
	
	public void setBackupServer(BackupServer bs) {
		
		if (bs != null) {
			Message backup = new Message(MessageType.BACKUP_SERVER, server.bs.getIp(), null, null, server.bs.getPort());
			connManager.sendMessage(backup);
		} else {
			Message backup = new Message(MessageType.BACKUP_SERVER, "", null, null, 0);
			connManager.sendMessage(backup);
		}
		
	}
	
	private void registerServer(Message connect) {
		log.log("ClientService: registering server");
		
		log.log("ClientService: backup server reg., got message: " + connect.getStr());
		
		clientName = connect.getStr();
		
		if (server.bs == null) {
			server.bs = new BackupServer(	connManager.getSocket().getInetAddress().toString(),
											Integer.parseInt(connect.getStr()), connManager);
			
			Message connected = new Message(MessageType.CONNECT);
			connManager.sendMessage(connected);
			
			logged = true;
			backup = true;
			
			server.announceBackupServer();
			
		} else {
			log.log("ClientService: error, only one backup server possible");
		}
		
	}
	
	private void registerClient(Message connect) {
		
		log.log("ClientService: registering client");
		
		log.log("ClientService got message: " + connect.getStr());
		clientName = connect.getStr();
		if (server.addClient(this, clientName)) {
			
			Message connected = new Message(MessageType.CONNECT);
			connManager.sendMessage(connected);
			if (server.bs != null) {
				//public Message(MessageType type, String str, ArrayList<String> list, String who, int roomID) {
				Message backup = new Message(MessageType.BACKUP_SERVER, server.bs.getIp(), null, null, server.bs.getPort());
				connManager.sendMessage(backup);
			}
			
			logged = true;
			
			setBackupServer(server.bs);
		
		} else {
			
			Message connected = new Message(MessageType.DISCONNECT, "Name already in use");
			connManager.sendMessage(connected);
			
			logged = false;
			running = false;
			
		}
		
	}
	
	synchronized void unregisterClient() {
		
			if (logged == true) {
				logged = false;
				log.log("ClientService: Unregistering client " + clientName);
				server.deleteClient(this);
				Message disconnected = new Message(MessageType.DISCONNECT);
				connManager.sendMessage(disconnected);
				running = false;
			}
		
	}
	
	private void unregisterServer() {
		
		log.log("ClientService: Unregistering server " + clientName);
		//server.deleteClient(this);
		if (disconnectMsg == true) {
			Message disconnected = new Message(MessageType.DISCONNECT, "Bye", false);
			connManager.sendMessage(disconnected);
		}
		
		running = false;
		
		server.bs = null;
		server.announceBackupServer();
		log.log("Backup Server dropped");
}
	
	private void runClient() {
		Message msg = null;
		
		if (connManager == null)
			return;
		
		//this.running = true;
		
		while (running) {
			
			msg = connManager.receiveMessage();
			if (msg != null) {
				MessageType c = msg.getType();
				
				switch(c) {
					case DISCONNECT://DISCONNECT 
						unregisterClient();     
						break;
					case ROOM_CREATE://ROOM_CREATE
						creatingRoom(msg.getStr());
						break;
					case ROOM_LEAVE://ROOM_LEAVE
						leaveRoom(msg.getStr());
						break;
					case MESSAGE://MESSAGE
						forwardMessage(msg.getWho(), msg.getRoomID(), msg.getStr(), msg.getList());
						break;
					case ROOM_INVITE:
						roomManager.addClientToRoom(msg.getRoomID(), msg.getStr());
						break;
					default:
						log.log("Unexpected MessageType: " + MessageType.MESSAGE.toString());
						break;
				}
			}
			
		}
		
		log.log("Client Service: ending connection with client " + clientName);
		
		try {
			connManager.close();
		} catch (Exception e) {
			e.printStackTrace();
			log.log("ClientService: unable to close streams or socket");
		}
	}
	
	
	private void runServer() {
		Message msg = null;
		
		if (connManager == null)
			return;
		
		this.running = true;
		
		while (running) {
			
			msg = connManager.receiveMessage();
			
			if (msg != null) {
				MessageType c = msg.getType();
				
				switch(c) {
					case DISCONNECT:
						disconnectMsg = true;
						unregisterServer();
						break;
					default:
						log.log("Unexpected MessageType: " + MessageType.MESSAGE.toString());
						break;
				}
			}
			
		}
		
		log.log("Client Service: ending connection with server " + clientName);
		
		try {
			connManager.close();
		} catch (Exception e) {
			e.printStackTrace();
			log.log("ClientService: unable to close streams or socket");
		}
	}
	
	private void creatingRoom(String name) {
		
		int roomID = roomManager.addRoom();
		roomManager.addClientToRoom(roomID, clientName);
		roomManager.addClientToRoom(roomID, name);
	}
	
	synchronized void send(ControlMessage m) {
		connManager.sendMessage(m);
	}
	
	private void leaveRoom(String id) {
		roomManager.removeClientFromRoom(Integer.parseInt(id), clientName);
	}
	
	private void forwardMessage(String who, int roomID, String msg, ArrayList<String> list) {
		roomManager.sendMessage(who, roomID, msg, list);
	}
	
	public void run() {
		
		if (connManager == null)
			return;
		
		this.running = true;
		
		log.log("ClientService: starting for ip " + connManager.getSocket().getInetAddress());
		
		Message connect = connManager.receiveMessage();
		
		if ((connect != null) && (connect.getType() == MessageType.CONNECT)) {
			log.log("ClientService got message: " + connect.getStr());
			
			if (connect.client == true) {
				registerClient(connect);
				runClient();
			} else {
				registerServer(connect);
				runServer();
			}
			
		} else {
			if (connect == null)
				log.log("ClientService received null Message");
			else
				log.log("ClientService got unexpected message with Type" + connect.getType());
			
			return;
		}
		
	}
	
}
